extern crate reqwest;
#[macro_use]
#[warn(unused_imports)]
extern crate clap;
extern crate serde_json;
extern crate dirs;
extern crate rusqlite;
extern crate rand;
extern crate uuid;
extern crate rpassword;
// extern crate async_ssh;
// extern crate thrussh_keys;
// extern crate tokio_core;
// extern crate tokio_io;
// extern crate thrussh;
// extern crate thrussh_keys;
// extern crate futures;
// extern crate tokio;

use colog;
use std::io::Write;

mod run;
mod vps;
mod sec;
mod script;
mod async_ssh;
use colored::Colorize;
use run::AccountManager;
use run::RemoteAccount;
use run::AccountAction;

use vps::pull_to_db;
use vps::set_user;
use sec::AccountS;
use script::load;
use clap::Parser;

#[derive(Parser,Debug)]
#[clap(
    author = "qingluan",
    version = "1.0",
    about = r#"
    pull to pull all host.
## Script:
    +pull
    +push
    +ls
## Vps Server Controll:
    ++World [label] [num] -V
        # exmaple : ++Tokyo jp-server 4 -V
        # ++"New_Jersey","Chicago","Dallas","Seattle","Los_Angeles","Atlanta","Amsterdam","London","Frankfurt","Silicon_Valley","Sydney","Paris","Miami","Singapore","Tokyo"
    remove tag:[tag name]  -V
        # example: remove tag:Scan -V
    ls -V
        # list server 's status
## Vps file handle:
    get: files ... in server
        # example get .bashrc .bash_history -L someKeyHost
    put:
        # put localfile [remote complete Path] -L someHost
        # put localfile -L someHost
    check -L someHost

    schedule:
        schedule add [minute/hour/day] local_scripts -L someHost
        schedule del local_scripts -L someHost
        schedule start -L someHost
    
    service:
        start service-name
        stop service-name
        list
"#
)]
struct Opts {
    #[clap(short = 'o', long = "db_file", default_value = "~/.config/cache.db")]
    db_path:String,
    cmd:Vec<String>,
    #[clap(short = 'L', long = "host", default_value = "[NONE]")]
    host_key:String,

    #[clap(short = 'a', long = "account-mode")]
    account:bool,

    #[clap(short = 'V', long = "vps-vultr-mode")]
    vps:bool,

    #[clap(short = 'f', long = "filter", default_value="search")]
    filter_options:Vec<String>,

    #[clap(short = 'k', long = "api", default_value = "[NONE]")]
    api:String,

    
}

fn to_static(s: String) -> &'static str {
    Box::leak(s.into_boxed_str())
}

fn log_inif() {
    let mut clog = colog::builder();
    clog.format(|buf, record| {
        writeln!(
            buf,
            "{}",
            record.args()
        )
    });
    // clog.filter(None, log::LevelFilter::Warn);
    clog.init();
}

#[allow(unused)]
fn vps_handle(opt:&Opts){
    let home = dirs::home_dir().unwrap();
    let p = home.join(".config").join("cache.db");
    if opt.cmd[0] == "++World"{
        let tag = if opt.cmd.len() > 1{
            opt.cmd[1].to_string()
        }else{
            "world-tag".to_string()
        };
        vps::create_world(p.to_str().unwrap(), &tag);
    } else if opt.cmd[0].starts_with("++") && opt.cmd.len() == 3{
        vps::create_country(p.to_str().unwrap(), &opt.cmd[0].replace("++",""),&opt.cmd[1], opt.cmd[2].parse::<i32>().expect("Not int") );
    }else if opt.cmd[0].ends_with(".conf")&& std::path::Path::new(&opt.cmd[0]).exists() {
        println!("create by file {}",opt.cmd[0]);
        let tag = if opt.cmd.len() > 1{
            opt.cmd[1].to_string()
        }else{
            "Default-Tag".to_owned().to_string()
        };
        vps::create_by_file(p.to_str().unwrap(),&opt.cmd[0],&tag)
    }else if opt.cmd[0] == "ls" {
        vps::list_servers(p.to_str().unwrap());
    
    }else if opt.cmd[0] == "remove" && opt.cmd.len() == 2{
        vps::remove_vps_by_tag(p.to_str().unwrap(), &opt.cmd[1], &opt.filter_options);
    }else{
        println!("error cmd : {}", opt.cmd.join(" ").yellow().to_string())
    }
}

#[allow(unused)]
fn account_handle(opt:&Opts ){
    let pass = rpassword::read_password_from_tty(Some("Password: ".underline().bold().yellow().to_string().as_str())).unwrap();
    let mut acc = AccountS::new(&pass);
    if opt.cmd.len() == 0 {
        let _ = acc.ls().iter().map(|x|{
            println!("{} {}",x[..3].green(), x[3..].blue().bold());
        }).collect::<Vec<_>>();
    }else if opt.cmd.len() == 1{
        if opt.cmd[0] == "+change"{
            let pass = rpassword::read_password_from_tty(Some("Password: ".underline().bold().yellow().to_string().as_str())).unwrap();
            acc.change_pwd(&pass);
            std::process::exit(0);
        }
        for r in acc.search(&opt.cmd[0]){
            if let Some(res) = acc.get(&r){
                println!("{}{}\n{}","found : ".on_green(),r.on_yellow(), res);
            }
        }
    }else if opt.cmd.len() == 2 {
        let p = std::path::Path::new(&opt.cmd[1]);
        if p.exists() && opt.cmd[0] == "+" {
            acc.insert_file(&opt.cmd[1]);
        }else{
            acc.insert(&opt.cmd[0],&opt.cmd[1]);
            
        }
        acc.save();
    
    }else{
        let content = opt.cmd[1..].iter().map(|x| {
            if x.trim() == "+"{
                let pass = rpassword::read_password_from_tty(Some("X is : ".underline().bold().yellow().to_string().as_str())).unwrap();
                pass
            }else{
                x.to_string()
            }
        }).collect::<Vec<_>>().join("\n");
        acc.insert(&opt.cmd[0], &content);
        acc.save();
    }
}

fn main() {
    log_inif();
    let mut opts: Opts = Opts::parse();
    if opts.account{
        account_handle(&opts);
        std::process::exit(0);
    }
    if opts.vps{
        vps_handle(&opts);
        std::process::exit(0);
    }
    if opts.api != "[NONE]"{
        let _ = set_user(&opts.db_path, &opts.api);
        std::process::exit(0);
    }
    if opts.cmd.len() > 0{
        if opts.cmd[0]== "pull"{
            let _ = pull_to_db(&opts.db_path);
        }else{
            log::info!("{}", opts.cmd.join(" | ").on_yellow());
            run_cmd(&mut opts);
        }
    }
    // println!("Hello, world!");
}




fn run_cmd(opts:&mut Opts){
    if opts.cmd.len() > 0 {
        match opts.cmd[0].trim() {
            "+ls" | "+pull" |"+push"  => {
                let res = load(&opts.cmd[0][1..]).unwrap();
                log::info!("{}", &res);
                std::process::exit(0);
            },
            _ => {}
        };
    }
    if opts.cmd.len() > 0 && opts.host_key != "[NONE]" {
        let home = dirs::home_dir().unwrap();
        let p = home.join(".config").join("cache.db");
        if let Some(acc) = AccountManager::new(p.as_ref()) {
            if opts.cmd[0] == "put" && opts.cmd.len() == 3 {
                let dst = opts.cmd[2].clone();
                for f_str in &opts.cmd[1..] {
                    let f_str = f_str.clone();
                    acc.search_host(&opts.host_key).upload(&f_str, &dst);
                }
            } else if opts.cmd[0] == "ping" {
                acc.search_host(&opts.host_key).with(move |r| {
                    log::info!(
                        "{} {}@{}",
                        "online".bold().on_green(),
                        r.user.bright_yellow().on_blue(),
                        r.host.bright_white().on_blue(),
                    );
                });
            } else if opts.cmd[0] == "service"{
                let res = match opts.cmd[1].as_ref(){
                    "start" => {
                        acc.search_host(&opts.host_key).exec(&format!("service {} start", opts.cmd[2]), move |remote: RemoteAccount| {
                            log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                        });
                    },
                    "stop" => {
                        acc.search_host(&opts.host_key).exec(&format!("service {} stop", opts.cmd[2]) ,move |remote: RemoteAccount| {
                            log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                        });
                    },
                    "list" => {
                        acc.search_host(&opts.host_key).exec("service --status-all", move |remote: RemoteAccount| {
                            log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                        });
                    },
                    _ => {
                        // "".to_string()
                    }
                };
                // log::info!("{}",res);
            } else if opts.cmd[0] == "schedule"{
                log::info!("crontab remote task :{:?}",opts.cmd);
                match opts.cmd[1].as_ref(){
                    "add" => {
                        acc.search_host(&opts.host_key).schedule_task("add",&opts.cmd[2], &opts.cmd[3]);
                    },
                    "del" => {
                        acc.search_host(&opts.host_key).schedule_task("del",&opts.cmd[2], &opts.cmd[2]);
                    },
                    "start" => {
                        acc.search_host(&opts.host_key).schedule_task("start","", "");
                    },
                    "stop" => {
                        acc.search_host(&opts.host_key).schedule_task("stop","", "");
                    },
                    _ => {
                        log::info!("err : {:?}", opts.cmd);
                    }
                }
            }else if opts.cmd[0] == "put" && opts.cmd.len() >= 2 {
                
                let dst = match opts.cmd.len(){
                    2 => {
                        let id = opts.cmd[1].split("/").last().unwrap();
                        acc.search_host(&opts.host_key).upload(&opts.cmd[1], &format!("/tmp/{}",id));
                        format!("/tmp/{}",id)
                    },
                    3 => {
                        acc.search_host(&opts.host_key).upload(&opts.cmd[1], &opts.cmd[2]);
                        opts.cmd[2].clone()
                    },
                    _ =>  {
                        log::error!("{}", "put localfile [remote]".red().bold());
                        "".to_string()
                    },
                };
                log::info!("Upload to : {}",&dst);
                if dst.ends_with(".sh"){
                    log::info!("run bash: {}",&opts.cmd[2]);
                    acc.search_host(&opts.host_key).bash(&format!("bash \"{}\"",dst));
                }
            } else if opts.cmd[0] == "test" {
                for r in acc.search_host(&opts.host_key) {
                    log::info!(
                        "{} {}{}{} | {} ssh port :{} | tag {}| loc: {}",
                        "[db]".bold().on_green(),
                        r.user.on_yellow(),
                        "@".on_yellow(),
                        r.host.on_yellow(),
                        r.pwd.black().on_yellow(),
                        r.port,
                        r.tag.green(),
                        r.loc.green(),
                    );
                }
            } else if opts.cmd[0] == "stop" {
                // use std::sync::Arc;
                let rrr = opts.cmd[1].clone();
                let runner: &'static str = to_static(rrr);
                acc.search_host(&opts.host_key).with(move |mut r| {
                    r.kill(runner);
                })
            } else if opts.cmd[0] == "check" {
                acc.search_host(&opts.host_key).with(move |mut remote| {
                    remote.check(vec![
                        "python", "python3", "pip3", "wget", "curl", "ssserver", "apt-get",
                        "unzip", "git",
                    ])
                });

            } else if opts.cmd[0] == "down" {
                log::info!("{:?}",opts.cmd);
                match opts.cmd.len() {
                    2 => acc.search_host(&opts.host_key).down(&opts.cmd[1], None),
                    3 => acc
                        .search_host(&opts.host_key)
                        .down(&opts.cmd[1], Some(&opts.cmd[2])),
                    _ => log::error!("{}", "url must special !".red().bold()),
                }
            } else if opts.cmd[0] == "update" {
                let config_file = dirs::home_dir().unwrap().join(".mu-cache/config.toml");
                acc.search_host(&opts.host_key)
                    .upload(config_file.to_str().unwrap(), ".mu-cache/config.toml");
            
            } else if opts.cmd[0] == "enuma-elish"{
                acc.search_host(&opts.host_key).enuma_elish();
            } else if opts.cmd[0] == "get" && opts.cmd.len() > 1 {
                // let dst = opts.cmd[2].clone();
                for f_str in &opts.cmd[1..] {
                    let f_str = f_str.clone();
                    acc.search_host(&opts.host_key).download(&f_str);
                }
            } else if opts.cmd[0] == "exec" && opts.cmd.len() > 2 {
                let up_executor = opts.cmd[1].clone();
                let cmd_str = opts.cmd[2..].join(" ");
                acc.search_host(&opts.host_key).upload_exec(
                    &up_executor,
                    &cmd_str,
                    move |remote| {
                        log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                    },
                );
            } else {
                let mut cmd_str = if opts.cmd.len() >= 1 && opts.cmd[0].starts_with("+"){
                    // log::info!("{}", opts.cmd[0]);
                    load(&opts.cmd[0][1..]).unwrap()
                }else{
                    // log::info!("use :{}",cmd_str.yellow());
                    opts.cmd.join(" ")
                    
                };
                if opts.cmd.len()>1 && cmd_str.contains("$$1"){
                    for i in 1..opts.cmd.len(){
                        cmd_str = cmd_str.replace(&format!("$${}",i), &opts.cmd[i]);
                    }
                }
                match opts.cmd[0].trim() {
                    "+ls" | "+pull" |"+push"  => {
                        std::process::exit(0);
                    },
                    _ => {}
                };
                log::info!("{}", &cmd_str);
                
                acc.search_host(&opts.host_key)
                    .exec(&cmd_str, move |remote: RemoteAccount| {
                        log::info!("{}|\n{}", &remote.addr().blue(), &remote.res.green());
                    });
            }
        }
        std::process::exit(0);
    }
}
